#ifndef CMD_CC
#define CMD_CC
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<iterator>
#include<vector>
#include<map>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<string>
#include<iostream>
#include "tclap/CmdLine.h"
#include "cmd.hpp"
#include "def.hpp"
#include "log.hpp"

using namespace TCLAP; // using the command line parser
using namespace std;

namespace Puza{
  PuzaCMD* PuzaCMD::m_pInstance = NULL;
  
  PuzaCMD* PuzaCMD::Instance(){
    if (!m_pInstance)   // Only allow one instance of class to be generated.
      m_pInstance = new PuzaCMD;
    return m_pInstance;
  }
  
  PuzaCMD::PuzaCMD(){ }
  
  ModelType PuzaCMD::CheckModelType(string temp_model_type){
    if(temp_model_type.compare("fm")==0)
    	return MODEL_FM;
    if(temp_model_type.compare("cofm_shared_feature")==0)
    	return MODEL_COFM_SHARED_FEATURE;
    if(temp_model_type.compare("cofm_shared_space")==0)
    	return MODEL_COFM_SHARED_SPACE;
    if(temp_model_type.compare("cofm_reg")==0)
    	return MODEL_COFM_REG;
    return MODEL_ERROR;  
  }
  
  TaskType PuzaCMD::CheckTaskType(string temp_task_type){
    if(temp_task_type.compare("train")==0)
      return TASK_TRAIN;
    if(temp_task_type.compare("predict")==0)
      return TASK_PREDICT;
    if(temp_task_type.compare("train_validation")==0)
      return TASK_TRAIN_VALIDATION;
    return TASK_ERROR;
  }

  PointLossType PuzaCMD::CheckPointLossType(string temp_point_loss_type){
	  if(temp_point_loss_type.compare("square")==0)
	    return LOSS_SQUARE;
	  if(temp_point_loss_type.compare("logistic")==0)
	    return LOSS_LOGISTIC;
	  if(temp_point_loss_type.compare("huber")==0)
	    return LOSS_HUBER;
	  return LOSS_POINT_ERROR;
  }

  RankLossType PuzaCMD::CheckRankLossType(string temp_rank_loss_type){
  	  if(temp_rank_loss_type.compare("bpr")==0)
  	    return LOSS_BPR;
  	  if(temp_rank_loss_type.compare("mrc")==0)
  	    return LOSS_MRC;
  	  if(temp_rank_loss_type.compare("warp")==0)
  	    return LOSS_WARP;
  	  return LOSS_RANK_ERROR;
  }

  InputFormat PuzaCMD::CheckInputFormat(string temp_format){
    if(temp_format.compare("feature")==0)
      return INPUT_FEATURE;
    return INPUT_ERROR;
  }
  
  ProcessType PuzaCMD::CheckProcessType(string temp_type){
	  if(temp_type.compare("point") == 0)
		  return PROCESS_POINT;
	  if(temp_type.compare("rank") == 0)
		  return PROCESS_RANK;
	  return PROCESS_ERROR;
  }

  void PuzaCMD::ParseCMD(int argc, char* argv[]){
    CmdLine cmd("This is the program implementing many latent factor models for collaborating filtering and content filtering.", ' ', "0.1");
    
    /* Main Program Parameters */
    ValueArg<string> modelArg("","model","model name",true,"","string");
    cmd.add( modelArg );
    ValueArg<string> taskArg("","task","the task to be performed",true,"","string");
    cmd.add( taskArg );
    
    /* IO Parameters */
    ValueArg<string> inputFormatArg("","format","input format",false,"simple","string");
    cmd.add( inputFormatArg );
    ValueArg<string> trainArg("","train","train file",false,"train","string");
    cmd.add( trainArg );
    ValueArg<string> predictArg("","predict","predict file",false,"predict","string");
    cmd.add( predictArg );
    ValueArg<string> outputArg("","output","output file",false,"output","string");
    cmd.add( outputArg );
    ValueArg<int> outputModelArg("","output_model","output model file",false,-1,"int");
    cmd.add( outputModelArg );
    
    /* Task Parameters */
    ValueArg<double> trainThresholdArg("","threshold","the threshold for training and validation",false,1.0,"double");
    cmd.add( trainThresholdArg );
    ValueArg<string> trainTypeArg("","train_type","the threshold for training and validation",false,"point","string");
    cmd.add( trainTypeArg );
    ValueArg<string> testTypeArg("","test_type","the threshold for testing",false,"point","string");
    cmd.add( testTypeArg );

    /* Model Parameters */
    ValueArg<int> latentKArg("","factor","the number of latent factors",false,5,"int");
    cmd.add( latentKArg );
    ValueArg<double> stepArg("","step","the step for gradient descent",false,0.001,"double");
    cmd.add( stepArg );
    ValueArg<double> regArg("","reg","the regularization parameter",false,1.0,"double");
    cmd.add( regArg );
    ValueArg<int> totalIterArg("","iter","the total number of iterations",false,100,"int");
    cmd.add( totalIterArg );
    ValueArg<string> pointLossTypeArg("","pointloss","the pointwise loss function",false,"square","string");
    cmd.add( pointLossTypeArg );
    ValueArg<string> rankLossTypeArg("","rankloss","the pair-wise ranking loss function",false,"bpr","string");
    cmd.add( rankLossTypeArg );

    
    /* Parse */
    cmd.parse( argc, argv );
    
    /* Get Main Program Parameters */
    PuzaDEF::Instance()->CURRENT_MODEL_TYPE = CheckModelType(modelArg.getValue()); // check whether we support the model
    PuzaDEF::Instance()->CURRENT_TASK_TYPE = CheckTaskType(taskArg.getValue());
    PuzaDEF::Instance()->CURRENT_INPUT_FORMAT = CheckInputFormat(inputFormatArg.getValue());
    PuzaDEF::Instance()->CURRENT_POINT_LOSS = CheckPointLossType(pointLossTypeArg.getValue());
    PuzaDEF::Instance()->CURRENT_RANK_LOSS = CheckRankLossType(rankLossTypeArg.getValue());
    PuzaDEF::Instance()->TRAIN_TYPE = CheckProcessType(trainTypeArg.getValue());
    PuzaDEF::Instance()->TEST_TYPE = CheckProcessType(testTypeArg.getValue());

    if(PuzaDEF::Instance()->CURRENT_MODEL_TYPE == MODEL_ERROR){
      cout << "We currently only support 1) Factorization Machines (SGD)." << endl;
      exit(0);
    }
    
    if(PuzaDEF::Instance()->CURRENT_TASK_TYPE == TASK_ERROR){
      cout << "We currently only support 1) Train. 2) Train and Validation. 3) Train and Predict. 4) Train and Validation through tunning learning rate. 5) Train and Validation through tunning regularization parameter." << endl;
      exit(0);
    }
    
    PuzaDEF::Instance()->TRAIN_FILE_NAME = trainArg.getValue();
    PuzaDEF::Instance()->PREDICT_FILE_NAME = predictArg.getValue();
    PuzaDEF::Instance()->OUTPUT_FILE_NAME = outputArg.getValue();
    PuzaDEF::Instance()->FACTOR = latentKArg.getValue();
    PuzaDEF::Instance()->LEARNING_RATE = stepArg.getValue();
    PuzaDEF::Instance()->REG = regArg.getValue();
    PuzaDEF::Instance()->TOTAL_ITER = totalIterArg.getValue();
    PuzaDEF::Instance()->TRAIN_THRESHOLD = trainThresholdArg.getValue();
  }
}

#endif
